// ************************************************************* //
//                                                               //
//        Copyright (C) 2015 by Socionext Inc.                   //
//        All rights reserved.                                   //
//        Socionext Inc. Proprietary and Confidential.           //
//                                                               //
// ************************************************************* //
/*****************************************************************************/
/**
* @file em_memorycard.c
* @brief eMMC memory caed
* @note None
* @attention None
*
*/
/*****************************************************************************/

// include -------------------------------------
// #include "common.h"
#include "em_int_cmn.h"
#include "em_memorycard.h"
#include "em_host.h"
#include "em_command.h"
#include "em_data.h"

#undef FILEID
#define FILEID FILE_EM_MEMORYCARD

// function -------------------------------------

static unsigned long em_getMaxSupportFrequency(
	unsigned long *p_max_frequency,
	unsigned char uc_tran_speed
);

static unsigned long em_memoryCard_calcDevCapa(
	EM_DEVICE* pDevice
);

/*****************************************************************************/
/**
 * @brief initialize memory card
 * @param[in] pSlot
 * @return EM_OK
 * @return EM_INVAL
 * @return EM_ERR_PARAM_ERR
 * @return EM_ERR_DEV_NULL_POINTER
 */
/*****************************************************************************/
unsigned long em_memoryCard_Initialize(
	EM_SLOT *pSlot
){
	unsigned long ul_Ret = EM_INVAL;
	EM_DEVICE* pDevice = NULL;
	/**
	 * parameter check
	 */
	if( pSlot == NULL ) {
		DEBUG_TRACE_INFO_32( 0, EM_ERR_PARAM_ERR );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_ERR_PARAM_ERR;
	}

	/**
	 * Process start
	 */

	pDevice = pSlot->pDevice;
	if ( pDevice == NULL )
	{
		DEBUG_TRACE_INFO_32( 0, EM_ERR_DEV_NULL_POINTER );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_ERR_DEV_NULL_POINTER;
	}

	// calculate device capacity
	ul_Ret = em_memoryCard_calcDevCapa(pDevice);
	if ( ul_Ret != EM_OK )
	{
		DEBUG_TRACE_INFO_32( 0, ul_Ret );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_INVAL;
	}

	// version check
	if (pDevice->uc_SpecVersNumb < 4)
	{
		//! not supported
		DEBUG_TRACE_INFO_32( 0, pDevice->uc_SpecVersNumb );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_INVAL;
	}
	else
	{
		pDevice->uc_SupportedBusWidths = (EM_BUS_WIDTH_1 | EM_BUS_WIDTH_4 | EM_BUS_WIDTH_8);
	}


	// select card
	ul_Ret = em_CmdSelectCard( pSlot, pDevice->us_RCA, EM_DISABLE );
	if( ul_Ret != EM_OK )
	{
		DEBUG_TRACE_INFO_32( 0, ul_Ret );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_INVAL;
	}

	// set block length
	ul_Ret = em_CmdSetBlockLen(
		pSlot,
		(unsigned long)(pDevice->sCardInfo.us_BlockSize),
		EM_DISABLE
	);
	if ( ul_Ret != EM_OK )
	{
		DEBUG_TRACE_INFO_32( 0, ul_Ret );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_INVAL;
	}

	return EM_OK;
}

/*****************************************************************************/
/**
 * @brief get CSD
 * @param[in] pDevice
 * @return EM_OK
 * @return EM_ERR_PARAM_ERR
 * @return EM_ERR_DEV_NULL_POINTER
 */
/*****************************************************************************/
unsigned long em_memoryCard_GetCSD(
	EM_DEVICE* pDevice
){
	EM_MEMORY_CARD_INFO *pCard = NULL;
	EM_SLOT *pSlot = NULL;
	unsigned long ul_Ret = EM_INVAL;

	/**
	 * parameter check
	 */
	if( pDevice == NULL ) {
		DEBUG_TRACE_INFO_32( 0, EM_ERR_PARAM_ERR );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_ERR_PARAM_ERR;
	}

	/**
	 * Process start
	 */
	if( pDevice->pSlot == NULL )
	{
		DEBUG_TRACE_INFO_32( 0, EM_ERR_DEV_NULL_POINTER );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_ERR_DEV_NULL_POINTER;
	}
	pSlot = pDevice->pSlot;
	pCard = &(pDevice->sCardInfo);

	// CSD
	ul_Ret = em_CmdReadCSD(pSlot, &(pSlot->ul_CSDinfo[0]));
	if ( ul_Ret != EM_OK )
	{
		DEBUG_TRACE_INFO_32( 0, ul_Ret );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_INVAL;
	}

	// System specification version
	pDevice->uc_SpecVersNumb = (unsigned char)EM_GET_CSD_REG_SPEC_VERS( pSlot->ul_CSDinfo ) ;

	// get max support frequency
	ul_Ret = em_getMaxSupportFrequency(
		&(pCard->ul_max_frequency),
		(unsigned char)EM_GET_CSD_REG_TRAN_SPEED( pSlot->ul_CSDinfo )
	);
	if (ul_Ret != EM_OK)
	{
		DEBUG_TRACE_INFO_32( 0, ul_Ret );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_INVAL;
	}

	return EM_OK;
}

/*****************************************************************************/
/**
 * @brief calculate device capacity
 * @param[in] pDevice
 * @return EM_OK
 * @return EM_ERR_PARAM_ERR
 * @return EM_ERR_DEV_NULL_POINTER
 */
/*****************************************************************************/
static unsigned long em_memoryCard_calcDevCapa(
	EM_DEVICE* pDevice
){
	EM_MEMORY_CARD_INFO *pCard = NULL;
	EM_SLOT *pSlot = NULL;
	unsigned long ul_Ret = EM_INVAL;
	unsigned long ul_CSize = 0;
	unsigned long ul_CSizeMult = 0;
	unsigned long ul_ReadBlLen = 0;
	unsigned long ul_Mult = 0;
	unsigned long ul_BlockNR = 0;
	unsigned long ul_BlockLen = 0;

	/**
	 * parameter check
	 */
	if( pDevice == NULL ) {
		DEBUG_TRACE_INFO_32( 0, EM_ERR_PARAM_ERR );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_ERR_PARAM_ERR;
	}

	/**
	 * Process start
	 */
	if( pDevice->pSlot == NULL )
	{
		DEBUG_TRACE_INFO_32( 0, EM_ERR_DEV_NULL_POINTER );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_ERR_DEV_NULL_POINTER;
	}
	pSlot = pDevice->pSlot;
	pCard = &(pDevice->sCardInfo);

	/// Extends CSD
	ul_Ret = em_memExtCsdInfo( pSlot );
	if (ul_Ret != EM_OK)
	{
		DEBUG_TRACE_INFO_32( 0, ul_Ret );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_INVAL;
	}

	// Device Capacity
	ul_CSize = EM_GET_CSD_REG_C_SIZE( pSlot->ul_CSDinfo ) ;
	if ( ul_CSize == 0xFFF )
	{
		// Higher than 2GB
		pCard->ul_DeviceSizeMB =
			((GET_BYTE_FROM_BUFFER(pSlot->ul_ExtendsCSD, 215)) << 13) |
			((GET_BYTE_FROM_BUFFER(pSlot->ul_ExtendsCSD, 214)) << 5) |
			((GET_BYTE_FROM_BUFFER(pSlot->ul_ExtendsCSD, 213)) >> 3);

		// addressing mode : sector mode
		pDevice->uc_DeviceCapacity = EM_CAPACITY_HIGH;

	} else {
		// Up to 2GB
		ul_CSizeMult = EM_GET_CSD_REG_C_SIZE_MULT( pSlot->ul_CSDinfo ) ;
		ul_ReadBlLen = EM_GET_CSD_REG_READ_BL_LEN( pSlot->ul_CSDinfo );
		ul_Mult = (unsigned long)(1 << ( ul_CSizeMult + 2 ));
		ul_BlockLen = (unsigned long)(1 << ul_ReadBlLen);
		ul_BlockNR =  ( ul_CSize + 1 ) * ul_Mult;

		pCard->ul_DeviceSizeMB = ul_BlockNR * ul_BlockLen / ( 1024uL * 1024uL );
	}

	return EM_OK;

}

/*****************************************************************************/
/**
 * @brief data transfer
 * @param[in] pDevice
 * @param[in] ul_Sector
 * @param[in,out] pBuffer
 * @param[in] ul_BufferSize
 * @param[in] e_TransferDirection
 * @return EM_OK
 * @return EM_INVAL
 * @return EM_ERR_PARAM_ERR
 * @return EM_ERR_DEV_NULL_POINTER
 */
/*****************************************************************************/
unsigned long em_memoryCard_DataTransfer(
	EM_DEVICE* pDevice,
	unsigned long ul_Sector,
	unsigned char *pBuffer,
	unsigned long ul_BufferSize,
	EM_TRANSFER_DIRECTION e_TransferDirection
){
	EM_MEMORY_CARD_INFO *pCard = NULL;
	unsigned long ul_BlockCount = 0;
	unsigned long ul_argument = 0;
	unsigned short us_BlockLen = 0;
	unsigned long ul_Ret = EM_INVAL;
	unsigned char uc_DMA_Use = EM_DISABLE;
	/**
	 * parameter check
	 */
	if( pDevice == NULL ) {
		DEBUG_TRACE_INFO_32( 0, EM_ERR_PARAM_ERR );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_ERR_PARAM_ERR;
	}
	if( pBuffer == NULL ) {
		DEBUG_TRACE_INFO_32( 0, EM_ERR_PARAM_ERR );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_ERR_PARAM_ERR;
	}

	/**
	 * Process start
	 */

	// buffer size 512byte check
	if ((ul_BufferSize % EM_SECTOR_SIZE) != 0)
	{
		DEBUG_TRACE_INFO_32( 0, ul_BufferSize );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_ERR_INVALID_PARAMETER ;
	}
	// NULL check
	if( pDevice->pSlot == NULL )
	{
		DEBUG_TRACE_INFO_32( 0, EM_ERR_DEV_NULL_POINTER );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_ERR_DEV_NULL_POINTER;
	}
	// buffer size check
	if (ul_BufferSize == 0)
	{
		return EM_OK;
	}

	pCard = &(pDevice->sCardInfo);

	// block size check
	if (pCard->us_BlockSize != EM_CONFIG_DEFAULT_BLOCK_SIZE)
	{
		ul_Ret = em_memoryCard_SetBlockLength(pDevice, EM_CONFIG_DEFAULT_BLOCK_SIZE);
		if (ul_Ret != EM_OK)
		{
			DEBUG_TRACE_INFO_32( 0, ul_Ret );
			DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
			return EM_INVAL;
		}
	}

	// select card
	ul_Ret = em_CmdSelectCard( pDevice->pSlot, pDevice->us_RCA, EM_DISABLE );
	if( ul_Ret != EM_OK )
	{
		DEBUG_TRACE_INFO_32( 0, ul_Ret );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_INVAL;
	}

	// Device Capacity
	if (pDevice->uc_DeviceCapacity == EM_CAPACITY_HIGH)
	{
		// sector mode
		us_BlockLen = EM_SECTOR_SIZE;
		ul_argument = ul_Sector;
	} else {
		// byte mode
		us_BlockLen = pCard->us_BlockSize;
		// Data address is in byte units in a Standard Capacity SD memory card and MMC memory card
		ul_argument = (ul_Sector * EM_SECTOR_SIZE);
	}

	// block count
	ul_BlockCount = (ul_BufferSize - 1) / pCard->us_BlockSize + 1;

	// Read/Write command
	ul_Ret = em_CmdBlockReadWrite(
		pDevice->pSlot,
		ul_argument,
		ul_BlockCount,
		us_BlockLen,
		pBuffer,
		&(uc_DMA_Use),
		e_TransferDirection
		);
	if (ul_Ret != EM_OK)
	{
		DEBUG_TRACE_INFO_32( 0, ul_Ret );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_INVAL;
	}

	return EM_OK;
}

/*****************************************************************************/
/**
 * @brief set block length
 * @param[in] pDevice
 * @param[in] ul_BlockLength
 * @return EM_OK
 * @return EM_INVAL
 * @return EM_ERR_PARAM_ERR
 * @return EM_ERR_DEV_NULL_POINTER
 */
/*****************************************************************************/
unsigned long em_memoryCard_SetBlockLength(
	EM_DEVICE* pDevice,
	unsigned long ul_BlockLength
){
	unsigned long ul_Ret = EM_INVAL;
	/**
	 * parameter check
	 */
	if( pDevice == NULL ) {
		DEBUG_TRACE_INFO_32( 0, EM_ERR_PARAM_ERR );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_ERR_PARAM_ERR;
	}

	/**
	 * Process start
	 */
	if( pDevice->pSlot == NULL )
	{
		DEBUG_TRACE_INFO_32( 0, EM_ERR_PARAM_ERR );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_ERR_DEV_NULL_POINTER;
	}

	// select card
	ul_Ret = em_CmdSelectCard( pDevice->pSlot, pDevice->us_RCA, EM_DISABLE );
	if ( ul_Ret != EM_OK )
	{
		DEBUG_TRACE_INFO_32( 0, ul_Ret );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_INVAL;
	}

	// set block length
	ul_Ret = em_CmdSetBlockLen( pDevice->pSlot, ul_BlockLength, EM_DISABLE );
	if ( ul_Ret != EM_OK )
	{
		DEBUG_TRACE_INFO_32( 0, ul_Ret );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_INVAL;
	}

	return EM_OK;
}

/*****************************************************************************/
/**
 * @brief get max support frequency
 * @param[out] p_max_frequency
 * @param[in] uc_tran_speed
 * @return EM_OK
 * @return EM_INVAL
 * @return EM_ERR_PARAM_ERR
 */
/*****************************************************************************/
static unsigned long em_getMaxSupportFrequency(
	unsigned long *p_max_frequency,
	unsigned char uc_tran_speed
){
	unsigned long ul_freq_unit = 0;
	unsigned long ul_multi_fact = 0;
	unsigned long ul_frequancy = 0;
	// CSD/TRAN_SPEED/Multiplier factor
	static const unsigned short us_mult_facor_list[16] = {
		0, 100, 120,  130,  150, 200, 260, 300, 350, 400, 450, 520,  550,  600, 700, 800
	};

	/**
	 * parameter check
	 */
	if( p_max_frequency == NULL ) {
		DEBUG_TRACE_INFO_32( 0, EM_ERR_PARAM_ERR );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_ERR_PARAM_ERR ;
	}

	/**
	 * Process start
	 */
	ul_freq_unit = EM_GET_CSD_REG_FREQ_UNIT( uc_tran_speed );
	ul_multi_fact = EM_GET_CSD_REG_MULTI_FACT( uc_tran_speed );

	// Multiplier factor to KHz
	if( ul_multi_fact >= 16 )
		ul_multi_fact = 15;

	ul_frequancy = (unsigned long)(us_mult_facor_list[ ul_multi_fact ]);

	switch( ul_freq_unit )
	{
	case 0:
		// KHz
		break;
	case 1:
		// MHz
		ul_frequancy *= 10uL;
		break;
	case 2:
		// 10MHz
		ul_frequancy *= 100uL;
		break;
	case 3:
		// 100MHz
		ul_frequancy *= 1000uL;
		break;
	default:
		DEBUG_TRACE_INFO( 0, uc_tran_speed );
		DEBUG_TRACE( FACT_EM_SWFAIL, &g_sTracedata );
		return EM_ERR_INVALID_PARAMETER;
	}

	*p_max_frequency = ul_frequancy;

	return EM_OK;
}
